# Copyright (C) 2008 Paul Hummer
# 
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

__licence__ = 'GPLv2'
__copyright__ = '2008, Paul Hummer'
__author__ = 'Paul Hummer <paul@ironlionsoftware.no.spam.com>'


from backend.core.db.query import Query

class Empty: pass

class ModelMeta(type):

    def __new__(clazz, name, bases, attrs):
        if name == 'Model':
            return super(ModelMeta, clazz).__new__(clazz, name, bases, attrs)

        new_class = type.__new__(clazz, name, bases, attrs)

        if not getattr(new_class, 'Meta', None):
            new_class.Meta = Empty()

        if not getattr(new_class.Meta, 'table', None):
            new_class.Meta.table = name.lower()

        if not getattr(new_class.Meta, 'pk', None):
            new_class.Meta.pk = 'id'

        #q = Query.raw_sql(".schema %s" % new_class.Meta.table)
        q = Query.raw_sql("SELECT * FROM %s LIMIT 1" % new_class.Meta.table)
        if q:
            new_class._fields = [f[0] for f in q.description]
        else:
            new_class._fields = []
        return new_class

class Model(object):
    '''Model class - a base class for all the models supported by
    Entertainer'''

    class ValidationError(Exception): pass

    __metaclass__ = ModelMeta

    def __init__(self, *args, **kwargs):

        self.id = None
        self._new_record = True
        #I'm not sureif it's really safe to be adding args based on order,
        #so I've commented it out.
        #[setattr(self, self._fields[i], arg for i, arg in enumerate(args))]
        [setattr(self, k, v) for k, v in kwargs.iteritems()]
        self._changed = set()

    def __setattr__(self, name, value):

        if name != '_changed' and name in self._fields and hasattr(self, '_changed'):
            self._changed.add(name)
        self.__dict__[name] = value

    def _update(self):
        query = "UPDATE %s SET " % self.Meta.table
        query += ", ".join(['%s=%s' % f for f in self._changed])
        query += " WHERE %s='%s'" % self.Meta.pk

        values = [getattr(self, f) for f in self._changed]
        values.append(self._get_pk())

        Query.raw_sql(query, values)

    def _new_save(self):

        query = "INSERT INTO %s VALUES (%s)" % (self.Meta.table, ', '.join(["?"] * len(self._fields)))
        values = [getattr(self, f, None) for  f in self._fields]
        self.pk = Query.raw_sql(query, values)

    def _get_defaults(self):

        for k, v in getattr(self.Meta, 'defaults', {}).iteritems():
            if not getattr(self, k, None):
                if callable(v):
                    v = v()
                setattr(self, k, v)

    def delete(self):

        query = "DELETE FROM %s WHERE %s='%s'" % (self.Meta.table, self.Meta.pk)
        values = [getattr(self, self.Meta.pk)]
        Query.raw_sql(query, values)

    def is_valid(self):

        try:
            self._validate()
            return True
        except Model.ValidationError:
            return False

    def _validate(self):

        for k, v in getattr(self.Meta, 'validations', {}).iteritems():
            assert callable(v), 'Validator is not callable'
            value = getattr(self, k)
            if not v(self, value):
                raise Model.ValidationError, 'Improper value "%s" for %s' % (value, k)
        return True

    def save(self):

        self._get_defaults()
        self._validate()
        if self._new_record:
            if self._new_record:
                self._new_save()
                self._new_record = False
            else:
                self._update()

    @classmethod
    def get(self, **kwargs):
        return Query(model=self, conditions=kwargs)

    def _get_pk(self): return getattr(self, self.Meta.pk)
    def _set_pk(self, value): setattr(self, self.Meta.pk, value)
    pk = property(_get_pk, _set_pk)

class AbstractRelation(object):

    def __init__(self, model, field=None):

        self.model = model
        self.field = field

class ForeignKey(AbstractRelation):

    def __get__(self, instance, owner):
        if not self.field:
            self.field = '%s_id' % self.model.Meta.table
        conditions = {self.model.Meta.pk: getattr(instance, self.field)}
        return Query(model=self.model, conditions=conditions)[0]

class OneToMany(AbstractRelation):

    def __get__(self, instanct, owner):
        if not self.field:
            self.field = '%s_id' % instance.Meta.table
        conditions = {self.field: getattr(instance, instance.Meta.pk)}
        return Query(model=self.model, conditions=conditions)

#Type system does not yet work, but will allow for better validation than the
#current validation
class Type(object):

    _val = None

    def __init__(self, **kwargs): 
        for key in kwargs:
            self.__setattr__(key, kwargs[key])

    def __str__(self): return self._val

class Integer(Type):
    def __init__(self, **kwargs): Type.__init__(self, kwargs)

class Real(Type):
    def __init__(self, **kwargs): Type.__init__(self, kwargs)

class Text(Type):
    def __init__(self, **kwargs): Type.__init__(self, kwargs)

class Boolean(Type):
    def __init__(self, **kwargs): Type.__init__(self, kwargs)

class Blob(Type):
    def __init__(self): self.val = float(val)

